/*
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  Author: hakanai
 *
 */

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "sdkconfig.h"
#include "esp_log.h"
#include "pn532hsu.h"

//static const char *TAG = "PN532-DRV";

enum pn532_init_state_enum {
  PN532_STATE_READ_RFSTATUS = 0,
  PN532_STATE_READ_VERSION,
  PN532_STATE_GET_STATUS,
  PN532_STATE_SET_SCANNING,
  PN532_STATE_COMPLETE
};

#define MAXBUFSIZ 64

#ifdef CONFIG_PN532_DEBUG_ENABLE
static void printbuf(uint8_t *buffer, uint16_t len) {
  for(uint16_t i = 0; i < len; i++){
    printf("%.2x ", buffer[i]);
  }
}

#define pprint(a, b, c) do { printf("%s: ", a); \
  printbuf(b, c); puts(""); } while(0)
#endif

static int send_packet(const uint8_t *buffer, uint16_t len) {
  uint8_t cmd[MAXBUFSIZ] = { 0 };
  uint8_t sum = 0xd4;
  int i;

  if (len > MAXBUFSIZ || len < 1)
    return ESP_FAIL;

  /* Make header */
  for (i = 0; i < 9; i++)
    cmd[i] = 0xff;

  /* Padding */
  cmd[i++] = 0x00;
  cmd[i++] = 0xff;

  /* Set length */
  cmd[i++] = len + 1;
  cmd[i++] = -(len + 1);

  /* Set target id */
  cmd[i++] = 0xd4;

  for (int j = 0; j < len; j++) {
    cmd[i++] = buffer[j];
    sum += buffer[j];
  }

  cmd[i++] = -(sum);
  cmd[i++] = 0xd4;

  if (uart_write_bytes(CONFIG_PN532_UART_PORT_NUM,
        (const char *) cmd, i) != i)
    return ESP_FAIL;

#ifdef CONFIG_PN532_DEBUG_ENABLE
  pprint("sent", cmd, i);
#endif

  return ESP_OK;
}

static uint16_t read_buffer(uint8_t *resp, uint16_t len) {
  uint8_t c[1];
  uint16_t index = 0; // Bytes we have read

  while (index < len) {
    if (uart_read_bytes(CONFIG_PN532_UART_PORT_NUM, c,
          1, 100 / portTICK_PERIOD_MS) > 0) {
      resp[index++] = c[0];
    } else {
      break;
    }
  }

  return index;
}

static int recv_packet(uint8_t *buffer) {
  int len, j, i;
  const uint8_t PN532_ACK[] = {0, 0, 0xFF, 0, 0xFF, 0, 0, 0, 0xFF};
  uint8_t dlen[2];
  uint8_t sum;

  len = read_buffer(buffer, MAXBUFSIZ);

#ifdef CONFIG_PN532_DEBUG_ENABLE
  pprint("recv", buffer, len);
#endif

  i = sizeof(PN532_ACK);

  if (memcmp(buffer, PN532_ACK, i)) {
/*    ESP_LOGE(TAG, "Invalid frame");*/
    return ESP_FAIL;
  }

  dlen[0] = buffer[i++];
  dlen[1] = buffer[i++];

  if (len < 2 || (uint8_t)(dlen[0] + dlen[1]) != 0) {
/*    ESP_LOGE(TAG, "Incorrect length");*/
    return ESP_FAIL;
  }

  if (buffer[i++] != 0xd5) {
/*    ESP_LOGE(TAG, "Hostcheck failed");*/
    return ESP_FAIL;
  }

  dlen[0] -= 2;
  sum = 0xd5 + buffer[i++];

  for (j = 0; j < dlen[0]; j++) {
    sum += buffer[i + j];
  }

  if (((uint8_t)(sum + buffer[len - 2])) != 0 || buffer[len - 1] != 0) {
/*    ESP_LOGE(TAG, "Checksum error %d %d %d", buffer[len-1], sum, (uint8_t)(sum + buffer[len-1]));*/
    return ESP_FAIL;
  }

  for (j = 0; j < dlen[0]; j++) {
    buffer[j] = buffer[i++];
  }

  return dlen[0];
}

static int read_status() {
  const uint8_t cmd[2] = { 0x58, 0x00 };
  uint8_t buffer[MAXBUFSIZ] = { 0 };
  const uint8_t PN532_ACK[] = {0, 0, 0xFF, 0, 0xFF, 0};
  const uint8_t ack_len = sizeof(PN532_ACK);

  if (send_packet(cmd, 2) != ESP_OK) {
    return ESP_FAIL;
  }

  if (read_buffer(buffer, MAXBUFSIZ) != ack_len) {
    return ESP_FAIL;
  }

  if (memcmp(buffer, PN532_ACK, ack_len)) {
    return ESP_FAIL;
  }

#ifdef CONFIG_PN532_DEBUG_ENABLE
  pprint("[ack]", buffer, ack_len);
#endif

  return ESP_OK;
}

static int read_diagnose() {
  const uint8_t cmd[1] = { 0x04 };
  uint8_t buffer[MAXBUFSIZ] = { 0 };
  int rlen;

  if (send_packet(cmd, 1) != ESP_OK) {
    return ESP_FAIL;
  }

  if ((rlen = recv_packet(buffer)) < 1) {
    return ESP_FAIL;
  }

  return ESP_OK;
}

static int read_version() {
  const uint8_t cmd[1] = { 0x02 };
  uint8_t buffer[MAXBUFSIZ] = { 0 };
  int rlen;

  if (send_packet(cmd, 1) != ESP_OK) {
    return ESP_FAIL;
  }

  if ((rlen = recv_packet(buffer)) < 3) {
    return ESP_FAIL;
  }

#ifdef CONFIG_PN532_DEBUG_ENABLE
  printf("PN532 ver %d.%d, features: %d\n",
      buffer[1], buffer[2], buffer[3]);
#endif

  return ESP_OK;
}

static int set_scanning() {
  const uint8_t cmd[5] = { 0x32, 0x05, 0xff, 0x01, 0x10 };
  uint8_t buffer[MAXBUFSIZ] = { 0 };
  int rlen;

  if (send_packet(cmd, 5) != ESP_OK) {
    return ESP_FAIL;
  }

  if ((rlen = recv_packet(buffer)) != 0) {
    return ESP_FAIL;
  }

#ifdef CONFIG_PN532_DEBUG_ENABLE
  printf("PN532 mode set\n");
#endif

  return ESP_OK;
}

static int pn532_begin() {
  uint8_t test_state = PN532_STATE_READ_RFSTATUS;
  short tries = 0;

  while (tries++ < 200 && test_state != PN532_STATE_COMPLETE) {
    switch (test_state) {
      case PN532_STATE_READ_RFSTATUS:
        if (read_status() == ESP_OK)
          test_state = PN532_STATE_READ_VERSION;
        break;
      case PN532_STATE_READ_VERSION:
        if (read_version() == ESP_OK)
          test_state = PN532_STATE_GET_STATUS;
        break;
      case PN532_STATE_GET_STATUS:
        if (read_diagnose() == ESP_OK)
          test_state = PN532_STATE_SET_SCANNING;
        break;
      case PN532_STATE_SET_SCANNING:
        if (set_scanning() == ESP_OK) {
          test_state = PN532_STATE_COMPLETE;
          return ESP_OK;
        }
        break;
    }
  }

  return ESP_FAIL;
}

static int pn532_uart_init() {
  uart_config_t uart_config = {
    .baud_rate = 115200,
    .data_bits = UART_DATA_8_BITS,
    .parity    = UART_PARITY_DISABLE,
    .stop_bits = UART_STOP_BITS_1,
    .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
    .source_clk = UART_SCLK_DEFAULT,
  };

  int intr_alloc_flags = 0;
  int err;

#if CONFIG_UART_ISR_IN_IRAM
  intr_alloc_flags = ESP_INTR_FLAG_IRAM;
#endif

  err = uart_driver_install(CONFIG_PN532_UART_PORT_NUM, 1024 * 2,
      0, 0, NULL, intr_alloc_flags);

  if (err != ESP_OK)
    return err;

  err = uart_param_config(CONFIG_PN532_UART_PORT_NUM, &uart_config);

  if (err != ESP_OK)
    return err;

  err = uart_set_pin(CONFIG_PN532_UART_PORT_NUM,
        CONFIG_PN532_UART_TXD, CONFIG_PN532_UART_RXD, -1, -1);

  return err;
}

int pn532_read(uint8_t *buf, int len) {
  const uint8_t cmd[3] = { 0x4a, 0x02, 0x00 };
  uint8_t buffer[MAXBUFSIZ] = { 0 };
  int rlen;

  if (send_packet(cmd, 3) != ESP_OK) {
    return ESP_FAIL;
  }

  if ((rlen = recv_packet(buffer)) < 1) {
    return ESP_FAIL;
  }

  if (buffer[0] == 0x00) { // (No card) skip
    return ESP_FAIL;
  }

  if (rlen > len)
    return ESP_FAIL;

  memcpy(buf, buffer, rlen);

#ifdef CONFIG_PN532_DEBUG_ENABLE
  printf("Read (%d) bytes:", rlen);
  printbuf(buffer, rlen);
  puts("");
#endif

  return rlen;
}

int pn532_decode_card(uint8_t *buf, int buf_len, uint8_t *out) {
  uint8_t id_len;

  if (buf_len < 7 || buf_len > MAXBUFSIZ)
    return ESP_FAIL;

  id_len = buf[5];

  if ((id_len + 5) > buf_len)
    return ESP_FAIL;

  for (uint8_t i = 0; i < id_len; i++) {
    out[i] = buf[6 + i];
  }

  return id_len;
}

int pn532_init() {
  int err;
  err = pn532_uart_init();

  if (err != ESP_OK)
    return err;

  err = pn532_begin();

  return err;
}

